package org.tp8.sampleapp.service.abs;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.Serializable;

import org.apache.commons.io.IOUtils;
import org.bson.types.ObjectId;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.tp8.sampleapp.dms.model.AbstractDocModel;
import org.tp8.sampleapp.dms.model.Attachment;
import org.tp8.sampleapp.dms.repository.BaseDocRepository;
import org.tp8.sampleapp.exc.SampleAppException;
import org.tp8.sampleapp.service.BaseDocService;

import com.mongodb.gridfs.GridFS;
import com.mongodb.gridfs.GridFSDBFile;
import com.mongodb.gridfs.GridFSInputFile;

/**
 * The Class AbstractEntityService.
 * 
 * @param <K>
 *            the key type
 * @param <T>
 *            the generic type
 * @param <R>
 *            the generic type
 */
@Transactional(propagation = Propagation.REQUIRED)
@Service
public abstract class AbstractDocService<K extends Serializable, T extends AbstractDocModel<K>, R extends BaseDocRepository<K, T>>
		extends AbstractService implements BaseDocService<K, T> {

	/** The Constant serialVersionUID. */
	private static final long serialVersionUID = 8036863836325962357L;

	/**
	 * The mongo.
	 */
	@Autowired
	private transient MongoTemplate mongoTemplate;

	/**
	 * The grid fs.
	 */
	private GridFS gridFS;

	/**
	 * Gets the repository.
	 * 
	 * @return the repository
	 */
	public abstract R getRepository();

	/**
	 * Gets the grid fs.
	 * 
	 * @return the grid fs
	 */
	public GridFS getGridFS() {
		if (gridFS == null) {
			gridFS = new GridFS(mongoTemplate.getDb(), "gridfs");
		}
		return gridFS;
	}

	/**
	 * Save attachment. Dec 01, 2013 10:35:55 PM
	 * org.apache.catalina.startup.Catalina start
	 * 
	 * @param attachment
	 *            the attachment
	 * @return the object id
	 */
	public ObjectId saveAttachment(Attachment attachment) {
		GridFSInputFile gfsFile = getGridFS().createFile(attachment.getData());
		gfsFile.setFilename(attachment.getFilename());
		gfsFile.setContentType(attachment.getMimeType());
		gfsFile.save();
		return (ObjectId) gfsFile.getId();
	}

	/**
	 * Dec 01, 2013 10:35:55 PM org.apache.catalina.startup.Catalina start
	 * 
	 * Gets the attachment.
	 * 
	 * @param id
	 *            the id
	 * @return the attachment
	 * @throws IOException
	 *             Signals that an I/O exception has occurred.
	 */
	public Attachment fetchAttachment(ObjectId id) throws IOException {
		Attachment attachment;
		ByteArrayOutputStream bos = new ByteArrayOutputStream();
		GridFSDBFile file = getGridFS().find(id);
		if (file != null) {
			attachment = new Attachment();
			attachment.setId(id);
			attachment.setFilename(file.getFilename());
			attachment.setMimeType(file.getContentType());
			InputStream inputstream = file.getInputStream();
			IOUtils.copy(inputstream, bos);
			IOUtils.closeQuietly(inputstream);
			IOUtils.closeQuietly(bos);
			attachment.setData(bos.toByteArray());
		} else {
			attachment = null;
		}
		return attachment;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * org.tp8.sampleapp.service.BaseDocService#save(org.tp8.sampleapp.dms.model
	 * .AbstractDocModel)
	 */
	@SuppressWarnings("unchecked")
	@Override
	public String save(T t) throws SampleAppException {
		Attachment attachment = t.getAttachment();
		if (attachment != null) {
			ObjectId attachmentId = saveAttachment(attachment);
			attachment.setId(attachmentId);
			t.setAttachmentId(attachmentId);
		}
		getRepository().save(t);
		return t.getId().toString();
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * org.tp8.sampleapp.service.BaseDocService#delete(java.io.Serializable)
	 */
	@Override
	public void delete(K id) throws SampleAppException {
		getRepository().delete(id);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * org.tp8.sampleapp.service.BaseDocService#findOne(java.io.Serializable)
	 */
	@Override
	public T findOne(K id) throws SampleAppException {
		T t = getRepository().findOne(id);
		if (t == null) {
			throw new SampleAppException("No document with id: " + id);
		}
		ObjectId attachmentId = t.getAttachmentId();
		Attachment attachment;
		if (attachmentId != null) {
			try {
				attachment = fetchAttachment(attachmentId);
			} catch (IOException e) {
				throw new SampleAppException(e);
			}
			t.setAttachment(attachment);
		}
		return t;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * org.tp8.sampleapp.service.BaseDocService#findAttachment(java.io.Serializable
	 * )
	 */
	@Override
	public byte[] findAttachment(K id) throws SampleAppException {
		T t = findOne(id);
		Attachment attachment = t.getAttachment();
		byte[] bytes;
		if (attachment != null) {
			bytes = attachment.getData();
		} else {
			throw new SampleAppException("No attachment was found");
		}
		return bytes;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * org.tp8.sampleapp.service.BaseDocService#findMetadata(java.io.Serializable
	 * )
	 */
	@Override
	public T findMetadata(K id) throws SampleAppException {
		return getRepository().findOne(id);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * org.tp8.sampleapp.service.BaseDocService#findAll(org.springframework.
	 * data.domain.Pageable)
	 */
	@Override
	public Page<T> findAll(Pageable pageable) throws SampleAppException {
		return getRepository().findAll(pageable);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.tp8.sampleapp.service.BaseDocService#count()
	 */
	@Override
	public long count() throws SampleAppException {
		return getRepository().count();
	}

}
